package eval

import (
	"showen/num"
	"showen/obj"
	"strings"
	"unicode"
)

type Error struct {
	Message string
}

// Type returns the object type as a string
func (e *Error) Type() obj.ObjectType {
	return obj.ERROR_OBJ
}

// String returns the error message
func (e *Error) Inspect() string {
	return e.Message
}

// Character functions
func CharEqual(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 2 {
		return &Error{Message: "char=? requires exactly 2 arguments"}
	}

	if args[0].Type() != obj.CHARACTER_OBJ || args[1].Type() != obj.CHARACTER_OBJ {
		return &Error{Message: "char=? arguments must be characters"}
	}

	char1 := args[0].(*obj.CharacterObject)
	char2 := args[1].(*obj.CharacterObject)

	return &obj.BooleanObject{Value: char1.Value == char2.Value}
}

func CharLess(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 2 {
		return &Error{Message: "char<? requires exactly 2 arguments"}
	}

	if args[0].Type() != obj.CHARACTER_OBJ || args[1].Type() != obj.CHARACTER_OBJ {
		return &Error{Message: "char<? arguments must be characters"}
	}

	char1 := args[0].(*obj.CharacterObject)
	char2 := args[1].(*obj.CharacterObject)

	return &obj.BooleanObject{Value: char1.Value < char2.Value}
}

func CharGreater(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 2 {
		return &Error{Message: "char>? requires exactly 2 arguments"}
	}

	if args[0].Type() != obj.CHARACTER_OBJ || args[1].Type() != obj.CHARACTER_OBJ {
		return &Error{Message: "char>? arguments must be characters"}
	}

	char1 := args[0].(*obj.CharacterObject)
	char2 := args[1].(*obj.CharacterObject)

	return &obj.BooleanObject{Value: char1.Value > char2.Value}
}

func CharLessEqual(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 2 {
		return &Error{Message: "char<=? requires exactly 2 arguments"}
	}

	if args[0].Type() != obj.CHARACTER_OBJ || args[1].Type() != obj.CHARACTER_OBJ {
		return &Error{Message: "char<=? arguments must be characters"}
	}

	char1 := args[0].(*obj.CharacterObject)
	char2 := args[1].(*obj.CharacterObject)

	return &obj.BooleanObject{Value: char1.Value <= char2.Value}
}

func CharGreaterEqual(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 2 {
		return &Error{Message: "char>=? requires exactly 2 arguments"}
	}

	if args[0].Type() != obj.CHARACTER_OBJ || args[1].Type() != obj.CHARACTER_OBJ {
		return &Error{Message: "char>=? arguments must be characters"}
	}

	char1 := args[0].(*obj.CharacterObject)
	char2 := args[1].(*obj.CharacterObject)

	return &obj.BooleanObject{Value: char1.Value >= char2.Value}
}

// Case-insensitive character comparison functions
func CharCiEqual(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 2 {
		return &Error{Message: "char-ci=? requires exactly 2 arguments"}
	}

	if args[0].Type() != obj.CHARACTER_OBJ || args[1].Type() != obj.CHARACTER_OBJ {
		return &Error{Message: "char-ci=? arguments must be characters"}
	}

	char1 := args[0].(*obj.CharacterObject)
	char2 := args[1].(*obj.CharacterObject)

	return &obj.BooleanObject{Value: unicode.ToLower(char1.Value) == unicode.ToLower(char2.Value)}
}

func CharCiLess(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 2 {
		return &Error{Message: "char-ci<? requires exactly 2 arguments"}
	}

	if args[0].Type() != obj.CHARACTER_OBJ || args[1].Type() != obj.CHARACTER_OBJ {
		return &Error{Message: "char-ci<? arguments must be characters"}
	}

	char1 := args[0].(*obj.CharacterObject)
	char2 := args[1].(*obj.CharacterObject)

	return &obj.BooleanObject{Value: unicode.ToLower(char1.Value) < unicode.ToLower(char2.Value)}
}

func CharCiGreater(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 2 {
		return &Error{Message: "char-ci>? requires exactly 2 arguments"}
	}

	if args[0].Type() != obj.CHARACTER_OBJ || args[1].Type() != obj.CHARACTER_OBJ {
		return &Error{Message: "char-ci>? arguments must be characters"}
	}

	char1 := args[0].(*obj.CharacterObject)
	char2 := args[1].(*obj.CharacterObject)

	return &obj.BooleanObject{Value: unicode.ToLower(char1.Value) > unicode.ToLower(char2.Value)}
}

func CharCiLessEqual(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 2 {
		return &Error{Message: "char-ci<=? requires exactly 2 arguments"}
	}

	if args[0].Type() != obj.CHARACTER_OBJ || args[1].Type() != obj.CHARACTER_OBJ {
		return &Error{Message: "char-ci<=? arguments must be characters"}
	}

	char1 := args[0].(*obj.CharacterObject)
	char2 := args[1].(*obj.CharacterObject)

	return &obj.BooleanObject{Value: unicode.ToLower(char1.Value) <= unicode.ToLower(char2.Value)}
}

func CharCiGreaterEqual(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 2 {
		return &Error{Message: "char-ci>=? requires exactly 2 arguments"}
	}

	if args[0].Type() != obj.CHARACTER_OBJ || args[1].Type() != obj.CHARACTER_OBJ {
		return &Error{Message: "char-ci>=? arguments must be characters"}
	}

	char1 := args[0].(*obj.CharacterObject)
	char2 := args[1].(*obj.CharacterObject)

	return &obj.BooleanObject{Value: unicode.ToLower(char1.Value) >= unicode.ToLower(char2.Value)}
}

// Character predicate functions
func CharAlphabetic(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 1 {
		return &Error{Message: "char-alphabetic? requires exactly 1 argument"}
	}

	if args[0].Type() != obj.CHARACTER_OBJ {
		return &Error{Message: "char-alphabetic? argument must be a character"}
	}

	char := args[0].(*obj.CharacterObject)
	return &obj.BooleanObject{Value: unicode.IsLetter(char.Value)}
}

func CharNumeric(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 1 {
		return &Error{Message: "char-numeric? requires exactly 1 argument"}
	}

	if args[0].Type() != obj.CHARACTER_OBJ {
		return &Error{Message: "char-numeric? argument must be a character"}
	}

	char := args[0].(*obj.CharacterObject)
	return &obj.BooleanObject{Value: unicode.IsDigit(char.Value)}
}

func CharWhitespace(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 1 {
		return &Error{Message: "char-whitespace? requires exactly 1 argument"}
	}

	if args[0].Type() != obj.CHARACTER_OBJ {
		return &Error{Message: "char-whitespace? argument must be a character"}
	}

	char := args[0].(*obj.CharacterObject)
	return &obj.BooleanObject{Value: unicode.IsSpace(char.Value)}
}

func CharUpperCase(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 1 {
		return &Error{Message: "char-upper-case? requires exactly 1 argument"}
	}

	if args[0].Type() != obj.CHARACTER_OBJ {
		return &Error{Message: "char-upper-case? argument must be a character"}
	}

	char := args[0].(*obj.CharacterObject)
	return &obj.BooleanObject{Value: unicode.IsUpper(char.Value)}
}

func CharLowerCase(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 1 {
		return &Error{Message: "char-lower-case? requires exactly 1 argument"}
	}

	if args[0].Type() != obj.CHARACTER_OBJ {
		return &Error{Message: "char-lower-case? argument must be a character"}
	}

	char := args[0].(*obj.CharacterObject)
	return &obj.BooleanObject{Value: unicode.IsLower(char.Value)}
}

// Character conversion functions
func CharToInteger(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 1 {
		return &Error{Message: "char->integer requires exactly 1 argument"}
	}

	if args[0].Type() != obj.CHARACTER_OBJ {
		return &Error{Message: "char->integer argument must be a character"}
	}

	char := args[0].(*obj.CharacterObject)
	return &obj.NumberObject{Value: num.Integer(int64(char.Value))}
}

func IntegerToChar(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 1 {
		return &Error{Message: "integer->char requires exactly 1 argument"}
	}

	if args[0].Type() != obj.NUMBER_OBJ {
		return &Error{Message: "integer->char argument must be an integer"}
	}

	numObj := args[0].(*obj.NumberObject)
	if numObj.Value.Type() != num.INTEGER_TYPE {
		return &Error{Message: "integer->char argument must be an integer"}
	}

	intVal := int64(numObj.Value.(num.Integer))
	if intVal < 0 || intVal > 0x10FFFF { // Unicode range
		return &Error{Message: "integer->char argument out of valid Unicode range"}
	}

	return &obj.CharacterObject{Value: rune(intVal)}
}

// Character case conversion functions
func CharUpcase(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 1 {
		return &Error{Message: "char-upcase requires exactly 1 argument"}
	}

	if args[0].Type() != obj.CHARACTER_OBJ {
		return &Error{Message: "char-upcase argument must be a character"}
	}

	char := args[0].(*obj.CharacterObject)
	return &obj.CharacterObject{Value: unicode.ToUpper(char.Value)}
}

func CharDowncase(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 1 {
		return &Error{Message: "char-downcase requires exactly 1 argument"}
	}

	if args[0].Type() != obj.CHARACTER_OBJ {
		return &Error{Message: "char-downcase argument must be a character"}
	}

	char := args[0].(*obj.CharacterObject)
	return &obj.CharacterObject{Value: unicode.ToLower(char.Value)}
}

// String functions
func MakeString(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) < 1 || len(args) > 2 {
		return &Error{Message: "make-string requires 1 or 2 arguments"}
	}

	if args[0].Type() != obj.NUMBER_OBJ {
		return &Error{Message: "first argument to make-string must be a number"}
	}

	k := args[0].(*obj.NumberObject).Value
	kVal, ok := k.(num.Integer)
	if !ok {
		return &Error{Message: "first argument to make-string must be an integer"}
	} else if int64(kVal) < 0 {
		return &Error{Message: "first argument to make-string must be non-negative"}
	}

	// Default character is #\space (ASCII 32)
	ch := ' '
	if len(args) == 2 {
		if args[1].Type() != obj.CHARACTER_OBJ {
			return &Error{Message: "second argument to make-string must be a character"}
		}
		ch = args[1].(*obj.CharacterObject).Value
	}

	// Create string with k repetitions of ch
	result := make([]rune, int(kVal))
	for i := range result {
		result[i] = ch
	}

	return &obj.StringObject{Value: string(result)}
}

func String(args []obj.Object, env *obj.Environment) obj.Object {
	runes := make([]rune, len(args))
	for i, arg := range args {
		if arg.Type() != obj.CHARACTER_OBJ {
			return &Error{Message: "all arguments to string must be characters"}
		}
		runes[i] = arg.(*obj.CharacterObject).Value
	}
	return &obj.StringObject{Value: string(runes)}
}

func StringLength(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 1 {
		return &Error{Message: "string-length requires exactly 1 argument"}
	}

	if args[0].Type() != obj.STRING_OBJ {
		return &Error{Message: "argument to string-length must be a string"}
	}

	str := args[0].(*obj.StringObject).Value
	return &obj.NumberObject{Value: num.Integer(int64(len([]rune(str))))}
}

func StringRef(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 2 {
		return &Error{Message: "string-ref requires exactly 2 arguments"}
	}

	if args[0].Type() != obj.STRING_OBJ {
		return &Error{Message: "first argument to string-ref must be a string"}
	}

	if args[1].Type() != obj.NUMBER_OBJ {
		return &Error{Message: "second argument to string-ref must be a number"}
	}

	str := args[0].(*obj.StringObject).Value
	k := args[1].(*obj.NumberObject).Value

	kVal, ok := k.(num.Integer)
	if !ok {
		return &Error{Message: "second argument to string-ref must be an integer"}
	} else if int64(kVal) < 0 {
		return &Error{Message: "second argument to string-ref must be non-negative"}
	}

	runes := []rune(str)
	if int(kVal) >= len(runes) {
		return &Error{Message: "index out of bounds"}
	}

	return &obj.CharacterObject{Value: runes[int(kVal)]}
}

func StringSet(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 3 {
		return &Error{Message: "string-set! requires exactly 3 arguments"}
	}

	if args[0].Type() != obj.STRING_OBJ {
		return &Error{Message: "first argument to string-set! must be a string"}
	}

	if args[1].Type() != obj.NUMBER_OBJ {
		return &Error{Message: "second argument to string-set! must be a number"}
	}

	if args[2].Type() != obj.CHARACTER_OBJ {
		return &Error{Message: "third argument to string-set! must be a character"}
	}

	// Note: In a real implementation, we would need to modify the string in place
	// For now, we'll just return an error since strings are immutable in Go
	return &Error{Message: "string-set! not implemented (strings are immutable in this implementation)"}
}

func StringEqual(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 2 {
		return &Error{Message: "string=? requires exactly 2 arguments"}
	}

	if args[0].Type() != obj.STRING_OBJ || args[1].Type() != obj.STRING_OBJ {
		return &Error{Message: "arguments to string=? must be strings"}
	}

	str1 := args[0].(*obj.StringObject).Value
	str2 := args[1].(*obj.StringObject).Value

	return &obj.BooleanObject{Value: str1 == str2}
}

func StringCiEqual(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 2 {
		return &Error{Message: "string-ci=? requires exactly 2 arguments"}
	}

	if args[0].Type() != obj.STRING_OBJ || args[1].Type() != obj.STRING_OBJ {
		return &Error{Message: "arguments to string-ci=? must be strings"}
	}

	str1 := args[0].(*obj.StringObject).Value
	str2 := args[1].(*obj.StringObject).Value

	return &obj.BooleanObject{Value: strings.EqualFold(str1, str2)}
}

func StringLess(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 2 {
		return &Error{Message: "string<? requires exactly 2 arguments"}
	}

	if args[0].Type() != obj.STRING_OBJ || args[1].Type() != obj.STRING_OBJ {
		return &Error{Message: "arguments to string<? must be strings"}
	}

	str1 := args[0].(*obj.StringObject).Value
	str2 := args[1].(*obj.StringObject).Value

	return &obj.BooleanObject{Value: str1 < str2}
}

func StringGreater(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 2 {
		return &Error{Message: "string>? requires exactly 2 arguments"}
	}

	if args[0].Type() != obj.STRING_OBJ || args[1].Type() != obj.STRING_OBJ {
		return &Error{Message: "arguments to string>? must be strings"}
	}

	str1 := args[0].(*obj.StringObject).Value
	str2 := args[1].(*obj.StringObject).Value

	return &obj.BooleanObject{Value: str1 > str2}
}

func StringLessEqual(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 2 {
		return &Error{Message: "string<=? requires exactly 2 arguments"}
	}

	if args[0].Type() != obj.STRING_OBJ || args[1].Type() != obj.STRING_OBJ {
		return &Error{Message: "arguments to string<=? must be strings"}
	}

	str1 := args[0].(*obj.StringObject).Value
	str2 := args[1].(*obj.StringObject).Value

	return &obj.BooleanObject{Value: str1 <= str2}
}

func StringGreaterEqual(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 2 {
		return &Error{Message: "string>=? requires exactly 2 arguments"}
	}

	if args[0].Type() != obj.STRING_OBJ || args[1].Type() != obj.STRING_OBJ {
		return &Error{Message: "arguments to string>=? must be strings"}
	}

	str1 := args[0].(*obj.StringObject).Value
	str2 := args[1].(*obj.StringObject).Value

	return &obj.BooleanObject{Value: str1 >= str2}
}

func StringCiLess(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 2 {
		return &Error{Message: "string-ci<? requires exactly 2 arguments"}
	}

	if args[0].Type() != obj.STRING_OBJ || args[1].Type() != obj.STRING_OBJ {
		return &Error{Message: "arguments to string-ci<? must be strings"}
	}

	str1 := args[0].(*obj.StringObject).Value
	str2 := args[1].(*obj.StringObject).Value

	return &obj.BooleanObject{Value: strings.ToLower(str1) < strings.ToLower(str2)}
}

func StringCiGreater(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 2 {
		return &Error{Message: "string-ci>? requires exactly 2 arguments"}
	}

	if args[0].Type() != obj.STRING_OBJ || args[1].Type() != obj.STRING_OBJ {
		return &Error{Message: "arguments to string-ci>? must be strings"}
	}

	str1 := args[0].(*obj.StringObject).Value
	str2 := args[1].(*obj.StringObject).Value

	return &obj.BooleanObject{Value: strings.ToLower(str1) > strings.ToLower(str2)}
}

func StringCiLessEqual(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 2 {
		return &Error{Message: "string-ci<=? requires exactly 2 arguments"}
	}

	if args[0].Type() != obj.STRING_OBJ || args[1].Type() != obj.STRING_OBJ {
		return &Error{Message: "arguments to string-ci<=? must be strings"}
	}

	str1 := args[0].(*obj.StringObject).Value
	str2 := args[1].(*obj.StringObject).Value

	return &obj.BooleanObject{Value: strings.ToLower(str1) <= strings.ToLower(str2)}
}

func StringCiGreaterEqual(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 2 {
		return &Error{Message: "string-ci>=? requires exactly 2 arguments"}
	}

	if args[0].Type() != obj.STRING_OBJ || args[1].Type() != obj.STRING_OBJ {
		return &Error{Message: "arguments to string-ci>=? must be strings"}
	}

	str1 := args[0].(*obj.StringObject).Value
	str2 := args[1].(*obj.StringObject).Value

	return &obj.BooleanObject{Value: strings.ToLower(str1) >= strings.ToLower(str2)}
}

func Substring(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 3 {
		return &Error{Message: "substring requires exactly 3 arguments"}
	}

	if args[0].Type() != obj.STRING_OBJ {
		return &Error{Message: "first argument to substring must be a string"}
	}

	if args[1].Type() != obj.NUMBER_OBJ || args[2].Type() != obj.NUMBER_OBJ {
		return &Error{Message: "second and third arguments to substring must be numbers"}
	}

	str := args[0].(*obj.StringObject).Value
	startNum := args[1].(*obj.NumberObject).Value
	endNum := args[2].(*obj.NumberObject).Value

	if startVal, ok := startNum.(num.Integer); !ok {
		return &Error{Message: "second argument to substring must be an integer"}
	} else if endVal, ok := endNum.(num.Integer); !ok {
		return &Error{Message: "third argument to substring must be an integer"}
	} else if int64(startVal) < 0 || int64(endVal) < 0 {
		return &Error{Message: "start and end indices must be non-negative"}
	} else if int64(startVal) > int64(endVal) {
		return &Error{Message: "start index must not be greater than end index"}
	} else {
		runes := []rune(str)
		if int(endVal) > len(runes) {
			return &Error{Message: "end index out of bounds"}
		}
		return &obj.StringObject{Value: string(runes[int(startVal):int(endVal)])}
	}
}

func StringAppend(args []obj.Object, env *obj.Environment) obj.Object {
	var sb strings.Builder
	for _, arg := range args {
		if arg.Type() != obj.STRING_OBJ {
			return &Error{Message: "all arguments to string-append must be strings"}
		}
		sb.WriteString(arg.(*obj.StringObject).Value)
	}
	return &obj.StringObject{Value: sb.String()}
}

/*
func StringToList(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 1 {
		return &Error{Message: "string->list requires exactly 1 argument"}
	}

	if args[0].Type() != obj.STRING_OBJ {
		return &Error{Message: "argument to string->list must be a string"}
	}

	str := args[0].(*obj.StringObject).Value
	runes := []rune(str)

	// Handle empty string case
	if len(runes) == 0 {
		return &obj.PairObject{Value: nil}
	}

	// Build list from runes (in reverse order)
	var result *ds.Pair = nil
	for i := len(runes) - 1; i >= 0; i-- {
		charObj := &obj.CharacterObject{Value: runes[i]}
		result = ds.NewPair(charObj, result)
	}

	return &obj.PairObject{Value: result}
}
*/
/*
func ListToString(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 1 {
		return &Error{Message: "list->string requires exactly 1 argument"}
	}

	// Handle empty list case
	if args[0].Type() == obj.NULL_OBJ {
		return &obj.StringObject{Value: ""}
	}

	if args[0].Type() != obj.PAIR_OBJ {
		return &Error{Message: "argument to list->string must be a list"}
	}

	pairObj := args[0].(*obj.PairObject)

	// Handle case where Value is nil (empty list)
	if pairObj.Value == nil {
		return &obj.StringObject{Value: ""}
	}

	// Extract characters from list
	var runes []rune
	current := pairObj.Value
	for current != nil {
		// Check if car is a character
		if charObj, ok := current.Car.(*obj.CharacterObject); ok {
			runes = append(runes, charObj.Value)
		} else {
			return &Error{Message: "all elements of list must be characters"}
		}

		// Move to next element
		if current.Cdr == nil {
			break
		}

		if nextPair, ok := current.Cdr.(*ds.Pair); ok {
			current = nextPair
		} else {
			// If Cdr is not a pair, it must be nil (end of list)
			if current.Cdr != nil {
				return &Error{Message: "malformed list"}
			}
			break
		}
	}

	return &obj.StringObject{Value: string(runes)}
}
*/
func List(args []obj.Object, env *obj.Environment) obj.Object {
	// Implementing the list procedure according to R4RS
	// (list obj1 obj2 ...) returns a newly allocated list of its arguments
	if len(args) == 0 {
		// Empty list
		return obj.EMPTY_LIST
	}

	// Create list by consing elements from right to left
	var result obj.Object = obj.EMPTY_LIST
	for i := len(args) - 1; i >= 0; i-- {
		// Convert obj.Object to ds.Pair compatible type
		/*var val any
		switch args[i].Type() {
		case obj.NUMBER_OBJ:
			val = args[i].(*obj.NumberObject).Value
		case obj.BOOLEAN_OBJ:
			val = args[i].(*obj.BooleanObject).Value
		case obj.CHARACTER_OBJ:
			val = args[i].(*obj.CharacterObject).Value
		case obj.PAIR_OBJ:
			val = args[i]
		case obj.STRING_OBJ:
			val = args[i].(*obj.StringObject).Value
		default:
			val = args[i]
		}
		result = &obj.PairObject{Car: val, Cdr: result}*/
		result = &obj.PairObject{Car: args[i], Cdr: result}
	}

	return result
}

func IsPairProc(args []obj.Object, env *obj.Environment) obj.Object {
	// Implementing the pair? predicate according to R4RS
	// pair? returns #t if obj is a pair, and otherwise returns #f
	if len(args) != 1 {
		return &Error{Message: "pair? requires exactly 1 argument"}
	}

	return &obj.BooleanObject{Value: args[0].Type() == obj.PAIR_OBJ}
}

// Type predicate functions according to R4RS
func IsBooleanProc(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 1 {
		return &Error{Message: "boolean? requires exactly 1 argument"}
	}

	return &obj.BooleanObject{Value: args[0].Type() == obj.BOOLEAN_OBJ}
}

func IsSymbolProc(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 1 {
		return &Error{Message: "symbol? requires exactly 1 argument"}
	}

	return &obj.BooleanObject{Value: args[0].Type() == obj.SYMBOL_OBJ}
}

func IsNumberProc(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 1 {
		return &Error{Message: "number? requires exactly 1 argument"}
	}

	return &obj.BooleanObject{Value: args[0].Type() == obj.NUMBER_OBJ}
}

func IsIntegerProc(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 1 {
		return &Error{Message: "integer? requires exactly 1 argument"}
	}

	if args[0].Type() != obj.NUMBER_OBJ {
		return &obj.BooleanObject{Value: false}
	}

	numObj := args[0].(*obj.NumberObject).Value
	return &obj.BooleanObject{Value: numObj.Type() == num.INTEGER_TYPE}
}

func IsRationalProc(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 1 {
		return &Error{Message: "rational? requires exactly 1 argument"}
	}

	if args[0].Type() != obj.NUMBER_OBJ {
		return &obj.BooleanObject{Value: false}
	}

	numObj := args[0].(*obj.NumberObject).Value
	// In R4RS, integers are also rationals
	return &obj.BooleanObject{Value: numObj.Type() == num.INTEGER_TYPE || numObj.Type() == num.RATIONAL_TYPE}
}

func IsRealProc(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 1 {
		return &Error{Message: "real? requires exactly 1 argument"}
	}

	if args[0].Type() != obj.NUMBER_OBJ {
		return &obj.BooleanObject{Value: false}
	}

	numObj := args[0].(*obj.NumberObject).Value

	// Handle complex numbers with zero imaginary part
	if numObj.Type() == num.COMPLEX_TYPE {
		complexNum := numObj.(num.Complex)
		// Use RNumber_CMP to compare imaginary part to zero (covers Integer/Rational/Real)
		if num.RNumber_CMP(complexNum.ImagPart, num.Integer(0)) == 0 {
			return &obj.BooleanObject{Value: true}
		}
		return &obj.BooleanObject{Value: false}
	}

	// In R4RS, integers and rationals are also reals
	return &obj.BooleanObject{Value: numObj.Type() == num.INTEGER_TYPE ||
		numObj.Type() == num.RATIONAL_TYPE ||
		numObj.Type() == num.REAL_TYPE}
}

func IsComplexProc(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 1 {
		return &Error{Message: "complex? requires exactly 1 argument"}
	}

	if args[0].Type() != obj.NUMBER_OBJ {
		return &obj.BooleanObject{Value: false}
	}

	numObj := args[0].(*obj.NumberObject).Value
	// In R4RS, all numbers are complex
	return &obj.BooleanObject{Value: numObj.Type() == num.INTEGER_TYPE ||
		numObj.Type() == num.RATIONAL_TYPE ||
		numObj.Type() == num.REAL_TYPE ||
		numObj.Type() == num.COMPLEX_TYPE}
}

func IsCharProc(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 1 {
		return &Error{Message: "char? requires exactly 1 argument"}
	}

	return &obj.BooleanObject{Value: args[0].Type() == obj.CHARACTER_OBJ}
}

func IsStringProc(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 1 {
		return &Error{Message: "string? requires exactly 1 argument"}
	}

	return &obj.BooleanObject{Value: args[0].Type() == obj.STRING_OBJ}
}

func IsProcedureProc(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 1 {
		return &Error{Message: "procedure? requires exactly 1 argument"}
	}

	objType := args[0].Type()
	return &obj.BooleanObject{Value: objType == obj.PROCEDURE_OBJ || objType == obj.BUILTIN_OBJ || objType == obj.LAMBDA_OBJ}
}

// I/O Procedures according to R4RS

func OpenInputFile(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 1 {
		return &Error{Message: "open-input-file requires exactly 1 argument"}
	}

	if args[0].Type() != obj.STRING_OBJ {
		return &Error{Message: "open-input-file argument must be a string"}
	}

	filename := args[0].(*obj.StringObject).Value

	// In a full implementation, we would open the file here
	// For now, we'll just create a port object to represent the file
	port := &obj.PortObject{
		File:    filename, // In a real implementation, this would be *os.File
		IsOpen:  true,
		IsInput: true,
	}

	return port
}

func OpenOutputFile(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 1 {
		return &Error{Message: "open-output-file requires exactly 1 argument"}
	}

	if args[0].Type() != obj.STRING_OBJ {
		return &Error{Message: "open-output-file argument must be a string"}
	}

	filename := args[0].(*obj.StringObject).Value

	// In a full implementation, we would open the file here
	// For now, we'll just create a port object to represent the file
	port := &obj.PortObject{
		File:    filename, // In a real implementation, this would be *os.File
		IsOpen:  true,
		IsInput: false,
	}

	return port
}

func CloseInputPort(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 1 {
		return &Error{Message: "close-input-port requires exactly 1 argument"}
	}

	if args[0].Type() != obj.PORT_OBJ {
		return &Error{Message: "close-input-port argument must be an input port"}
	}

	port := args[0].(*obj.PortObject)

	if !port.IsInput {
		return &Error{Message: "close-input-port argument must be an input port"}
	}

	// In a full implementation, we would close the file here
	// For now, we'll just mark the port as closed
	port.IsOpen = false

	return obj.NULL_OBJECT
}

func CloseOutputPort(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 1 {
		return &Error{Message: "close-output-port requires exactly 1 argument"}
	}

	if args[0].Type() != obj.PORT_OBJ {
		return &Error{Message: "close-output-port argument must be an output port"}
	}

	port := args[0].(*obj.PortObject)

	if port.IsInput {
		return &Error{Message: "close-output-port argument must be an output port"}
	}

	// In a full implementation, we would close the file here
	// For now, we'll just mark the port as closed
	port.IsOpen = false

	return obj.NULL_OBJECT
}

func EofObject(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 1 {
		return &Error{Message: "eof-object? requires exactly 1 argument"}
	}

	// Check if the argument is an EOF object
	return &obj.BooleanObject{Value: args[0].Type() == obj.EOF_OBJ}
}

func Cons(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 2 {
		return &Error{Message: "cons requires exactly 2 arguments"}
	}

	// Convert obj.Object to ds.Pair compatible types
	var car, cdr any

	// Handle car
	switch args[0].Type() {
	case obj.NUMBER_OBJ:
		car = args[0].(*obj.NumberObject).Value
	case obj.BOOLEAN_OBJ:
		car = args[0].(*obj.BooleanObject).Value
	case obj.CHARACTER_OBJ:
		car = args[0].(*obj.CharacterObject).Value
	default:
		car = args[0]
	}

	// Handle cdr
	switch args[1].Type() {
	case obj.NUMBER_OBJ:
		cdr = args[1].(*obj.NumberObject).Value
	case obj.BOOLEAN_OBJ:
		cdr = args[1].(*obj.BooleanObject).Value
	case obj.CHARACTER_OBJ:
		cdr = args[1].(*obj.CharacterObject).Value
	default:
		cdr = args[1]
	}

	return &obj.PairObject{Car: car, Cdr: cdr}
}

func Car(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 1 {
		return &Error{Message: "car requires exactly 1 argument"}
	}

	if args[0].Type() != obj.PAIR_OBJ {
		return &Error{Message: "car argument must be a pair"}
	}

	pairObj := args[0].(*obj.PairObject)

	car := pairObj.Car

	// Convert ds.Pair car value back to obj.Object
	return convertToObjObject(car)
}

func Cdr(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 1 {
		return &Error{Message: "cdr requires exactly 1 argument"}
	}

	if args[0].Type() != obj.PAIR_OBJ {
		return &Error{Message: "cdr argument must be a pair"}
	}

	pairObj := args[0].(*obj.PairObject)

	cdr := pairObj.Cdr

	// Convert cdr value back to obj.Object
	return convertToObjObject(cdr)
}

// convertToObjObject converts a PairObject value to an obj.Object
func convertToObjObject(value any) obj.Object {
	if value == nil {
		return obj.NULL_OBJECT
	}

	switch v := value.(type) {
	case *obj.PairObject:
		return v
	case *obj.EmptyList:
		return v
	case num.Number:
		return &obj.NumberObject{Value: v}
	case bool:
		return &obj.BooleanObject{Value: v}
	case rune:
		return &obj.CharacterObject{Value: v}
	case string:
		return &obj.StringObject{Value: v}
	case *obj.SymbolObject:
		return v
	case *obj.IdentifierObject:
		return v
	case *obj.NumberObject:
		return v
	case *obj.BooleanObject:
		return v
	case *obj.CharacterObject:
		return v
	case *obj.StringObject:
		return v
	default:
		// For other types, wrap in IdentifierObject for now
		// In a full implementation, we would need to handle all possible types
		return &obj.IdentifierObject{Value: ""}
	}
}
func Not(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 1 {
		return &Error{Message: "not requires exactly 1 argument"}
	}

	b := true
	arg := args[0]
	// In Scheme, anything that is not #f is considered true
	if arg.Type() == obj.BOOLEAN_OBJ {
		b = arg.(*obj.BooleanObject).Value
	}
	return &obj.BooleanObject{Value: go_not(b)}
}

func go_not(b bool) bool {
	return !b
}

/*
func Or(args []obj.Object, env *obj.Environment) obj.Object {
	// In Scheme, (or) with no arguments returns #f
	if len(args) == 0 {
		return &obj.BooleanObject{Value: false}
	}

	// Evaluate each argument and return #t if any is not #f, otherwise return #f
	for _, arg := range args {
		// In Scheme, only #f is false, everything else is true
		if !(arg.Type() == obj.BOOLEAN_OBJ && arg.(*obj.BooleanObject).Value == false) {
			return arg
		}
	}

	return &obj.BooleanObject{Value: false}

}*/
/*
func And(args []obj.Object, env *obj.Environment) obj.Object {
	//In Scheme, (and) with no arguments returns #t
	if len(args) == 0 {
		return &obj.BooleanObject{Value: true}
	}

	// Evaluate each argument and return #f if any is #f, otherwise return #t
	for _, arg := range args {
		// In Scheme, only #f is false, everything else is true
		if arg.Type() == obj.BOOLEAN_OBJ && arg.(*obj.BooleanObject).Value == false {
			return &obj.BooleanObject{Value: false}
		}
	}

	return args[len(args)-1]
}
*/
// Integer arithmetic operations
func Add(args []obj.Object, env *obj.Environment) obj.Object {
	// Handle case with no arguments - return 0
	if len(args) == 0 {
		return &obj.NumberObject{Value: num.Integer(0)}
	}
	var result num.Number
	result = num.Integer(0)
	for _, arg := range args {
		if arg.Type() != obj.NUMBER_OBJ {
			return &Error{Message: "addition expects integer, rational or real numbers"}
		}
		intArg := arg.(*obj.NumberObject)
		result = result.ADD(intArg.Value)
	}

	return &obj.NumberObject{Value: result}
}

func Subtract(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) == 0 {
		return &Error{Message: "subtraction requires at least one argument"}
	}

	if len(args) == 1 {
		// Unary minus
		if args[0].Type() != obj.NUMBER_OBJ {
			return &Error{Message: "unary minus expects a number"}
		}
		intArg := args[0].(*obj.NumberObject)
		return &obj.NumberObject{Value: num.Integer(0).SUB(intArg.Value)}
	}

	// Multiple arguments: subtract rest from first
	if args[0].Type() != obj.NUMBER_OBJ {
		return &Error{Message: "subtraction expects integer, rational or real numbers"}
	}

	var result num.Number
	result = args[0].(*obj.NumberObject).Value
	for _, arg := range args[1:] {
		if arg.Type() != obj.NUMBER_OBJ {
			return &Error{Message: "subtraction expects integer, rational or real numbers"}
		}
		intArg := arg.(*obj.NumberObject)
		result = result.SUB(intArg.Value)
	}

	return &obj.NumberObject{Value: result}
}

func Multiply(args []obj.Object, env *obj.Environment) obj.Object {
	// Handle case with no arguments - return 1
	if len(args) == 0 {
		return &obj.NumberObject{Value: num.Integer(1)}
	}
	var result num.Number
	result = num.Integer(1)
	for _, arg := range args {
		if arg.Type() != obj.NUMBER_OBJ {
			return &Error{Message: "multiplication expects integer, rational or real numbers"}
		}
		intArg := arg.(*obj.NumberObject)
		result = result.MUL(intArg.Value)
	}

	return &obj.NumberObject{Value: result}
}

func Divide(args []obj.Object, env *obj.Environment) obj.Object {
	// Handle case with no arguments
	if len(args) == 0 {
		return &Error{Message: "division requires at least one argument"}
	}

	// Handle case with one argument - return 1/arg
	if len(args) == 1 {
		if args[0].Type() != obj.NUMBER_OBJ {
			return &Error{Message: "division expects integer, rational or real numbers"}
		}
		intArg := args[0].(*obj.NumberObject)
		// 1 divided by arg
		return &obj.NumberObject{Value: num.Integer(1).DIV(intArg.Value)}
	}

	// Handle case with multiple arguments - divide first by rest
	if args[0].Type() != obj.NUMBER_OBJ {
		return &Error{Message: "division expects integer, rational or real numbers"}
	}

	var result num.Number
	result = args[0].(*obj.NumberObject).Value
	for _, arg := range args[1:] {
		if arg.Type() != obj.NUMBER_OBJ {
			return &Error{Message: "division expects integer, rational or real numbers"}
		}
		intArg := arg.(*obj.NumberObject)
		result = result.DIV(intArg.Value)
	}

	return &obj.NumberObject{Value: result}
}

/*
	func convertTofloat64(x obj.Object) (float64, error) {
		if x.Type() != obj.NUMBER_OBJ {
			return 0, errors.New("expected number")
		}
		if x.(*obj.NumberObject).Value.Type() == num.COMPLEX_TYPE {
			return 0, errors.New("cannot convert complex number to float64")
		}
		xx := x.(*obj.NumberObject).Value
		switch xx := xx.(type) {
		case num.Integer:
			return float64(xx.Value), nil
		case num.Rational:
			return float64(xx.Numerator) / float64(xx.Denominator), nil
		case num.Real:
			return xx.Value, nil
		}
		return 0, errors.New("cannot convert number to float64")
	}

	func greaterThan(args []obj.Object, env *obj.Environment) obj.Object {
		if len(args) < 2 {
			return &obj.BooleanObject{Value: true}
		}
		x, err := convertTofloat64(args[0])
		if err != nil {
			return &Error{Message: "expected number"}
		}
		for _, arg := range args[1:] {
			y, err := convertTofloat64(arg)
			if err != nil {
				return &Error{Message: "expected number"}
			}
			if x <= y {
				return &obj.BooleanObject{Value: false}
			}
			x = y
		}
		return &obj.BooleanObject{Value: true}
	}

	func lessThan(args []obj.Object, env *obj.Environment) obj.Object {
		if len(args) < 2 {
			return &obj.BooleanObject{Value: true}
		}
		x, err := convertTofloat64(args[0])
		if err != nil {
			return &Error{Message: "expected number"}
		}
		for _, arg := range args[1:] {
			y, err := convertTofloat64(arg)
			if err != nil {
				return &Error{Message: "expected number"}
			}
			if x >= y {
				return &obj.BooleanObject{Value: false}
			}
			x = y
		}
		return &obj.BooleanObject{Value: true}
	}

	func lessThanOrEqual(args []obj.Object, env *obj.Environment) obj.Object {
		if len(args) < 2 {
			return &obj.BooleanObject{Value: true}
		}
		x, err := convertTofloat64(args[0])
		if err != nil {
			return &Error{Message: "expected number"}
		}
		for _, arg := range args[1:] {
			y, err := convertTofloat64(arg)
			if err != nil {
				return &Error{Message: "expected number"}
			}
			if x > y {
				return &obj.BooleanObject{Value: false}
			}
			x = y
		}
		return &obj.BooleanObject{Value: true}
	}

	func equal(args []obj.Object, env *obj.Environment) obj.Object {
		if len(args) < 2 {
			return &obj.BooleanObject{Value: true}
		}
		x, err := convertTofloat64(args[0])
		if err != nil {
			return &Error{Message: "expected number"}
		}
		for _, arg := range args[1:] {
			y, err := convertTofloat64(arg)
			if err != nil {
				return &Error{Message: "expected number"}
			}
			if x != y {
				return &obj.BooleanObject{Value: false}
			}
			x = y
		}
		return &obj.BooleanObject{Value: true}
	}

	func greaterThanOrEqual(args []obj.Object, env *obj.Environment) obj.Object {
		if len(args) < 2 {
			return &obj.BooleanObject{Value: true}
		}
		x, err := convertTofloat64(args[0])
		if err != nil {
			return &Error{Message: "expected number"}
		}
		for _, arg := range args[1:] {
			y, err := convertTofloat64(arg)
			if err != nil {
				return &Error{Message: "expected number"}
			}
			if x < y {
				return &obj.BooleanObject{Value: false}
			}
			x = y
		}
		return &obj.BooleanObject{Value: true}
	}

	func GreaterThan(args []obj.Object, env *obj.Environment) obj.Object {
		if len(args) < 2 {
			return &obj.BooleanObject{Value: true}
		}
		x, err := convertTofloat64(args[0])
		if err != nil {
			return &Error{Message: "expected number"}
		}
		for _, arg := range args[1:] {
			y, err := convertTofloat64(arg)
			if err != nil {
				return &Error{Message: "expected number"}
			}
			if x <= y {
				return &obj.BooleanObject{Value: false}
			}
		}
		return &obj.BooleanObject{Value: true}
	}
*/

func lessThan(args []obj.Object, env *obj.Environment) obj.Object {
	if !checkRNumber(args) {
		return &Error{Message: "all shoulbe be integer, rational, or real numbers"}
	}
	if len(args) < 2 {
		return &obj.BooleanObject{Value: true}
	}
	x := args[0].(*obj.NumberObject).Value.(num.RNumber)
	for _, arg := range args[1:] {
		y := arg.(*obj.NumberObject).Value.(num.RNumber)
		if num.RNumber_CMP(x, y) != -1 {
			return &obj.BooleanObject{Value: false}
		}
		x = y
	}
	return &obj.BooleanObject{Value: true}
}

func lessThanOrEqual(args []obj.Object, env *obj.Environment) obj.Object {
	if !checkRNumber(args) {
		return &Error{Message: "all shoulbe be integer, rational, or real numbers"}
	}
	if len(args) < 2 {
		return &obj.BooleanObject{Value: true}
	}
	x := args[0].(*obj.NumberObject).Value.(num.RNumber)
	for _, arg := range args[1:] {
		y := arg.(*obj.NumberObject).Value.(num.RNumber)
		if num.RNumber_CMP(x, y) == 1 {
			return &obj.BooleanObject{Value: false}
		}
		x = y
	}
	return &obj.BooleanObject{Value: true}
}

func equal(args []obj.Object, env *obj.Environment) obj.Object {
	switch checkNumber(args) {
	case 0:
		return equalRNumber(args)
	case 1:
		return equalComplex(args)
	case 2:
		return &Error{Message: "Complex and RNumber cannot compare"}
	default:
		return &Error{Message: "all should be be real or complex numbers"}
	}
}

func equalRNumber(args []obj.Object) obj.Object {
	if len(args) < 2 {
		return &obj.BooleanObject{Value: true}
	}

	x := args[0].(*obj.NumberObject).Value.(num.RNumber)
	for _, arg := range args[1:] {
		y := arg.(*obj.NumberObject).Value.(num.RNumber)
		if num.RNumber_CMP(x, y) != 0 {
			return &obj.BooleanObject{Value: false}
		}
		x = y
	}

	return &obj.BooleanObject{Value: true}
}

func equalComplex(args []obj.Object) obj.Object {
	if len(args) < 2 {
		return &obj.BooleanObject{Value: true}
	}

	x := args[0].(*obj.NumberObject).Value.(num.Complex)
	for _, arg := range args[1:] {
		y := arg.(*obj.NumberObject).Value.(num.Complex)
		// Compare complex numbers by comparing real and imaginary parts
		if num.RNumber_CMP(x.RealPart, y.RealPart) != 0 || num.RNumber_CMP(x.ImagPart, y.ImagPart) != 0 {
			return &obj.BooleanObject{Value: false}
		}
		x = y
	}

	return &obj.BooleanObject{Value: true}
}

func greaterThanOrEqual(args []obj.Object, env *obj.Environment) obj.Object {
	if !checkRNumber(args) {
		return &Error{Message: "all shoulbe be integer, rational, or real numbers"}
	}
	if len(args) < 2 {
		return &obj.BooleanObject{Value: true}
	}
	x := args[0].(*obj.NumberObject).Value.(num.RNumber)
	for _, arg := range args[1:] {
		y := arg.(*obj.NumberObject).Value.(num.RNumber)
		if num.RNumber_CMP(x, y) == -1 {
			return &obj.BooleanObject{Value: false}
		}
		x = y
	}
	return &obj.BooleanObject{Value: true}
}

func greaterThan(args []obj.Object, env *obj.Environment) obj.Object {
	if !checkRNumber(args) {
		return &Error{Message: "all shoulbe be integer, rational, or real numbers"}
	}
	if len(args) < 2 {
		return &obj.BooleanObject{Value: true}
	}
	x := args[0].(*obj.NumberObject).Value.(num.RNumber)
	for _, arg := range args[1:] {
		y := arg.(*obj.NumberObject).Value.(num.RNumber)
		if num.RNumber_CMP(x, y) != 1 {
			return &obj.BooleanObject{Value: false}
		}
		x = y
	}
	return &obj.BooleanObject{Value: true}
}

func checkRNumber(args []obj.Object) bool {
	for _, arg := range args {
		if arg.Type() != obj.NUMBER_OBJ {
			return false
		}
		t := arg.(*obj.NumberObject).Value.Type()
		if t != num.INTEGER_TYPE && t != num.RATIONAL_TYPE && t != num.REAL_TYPE {
			return false
		}
	}
	return true
}

func checkNumber(args []obj.Object) int {
	// 0 - all RNumber
	// 1 - all Complex
	// 2 - mixed Complex and RNumber
	// 3 - not all numbers

	// First check if all arguments are numbers
	for _, arg := range args {
		if arg.Type() != obj.NUMBER_OBJ {
			return 3 // not all numbers
		}
	}

	// Check if we have any complex numbers
	hasComplex := false
	hasRNumber := false

	for _, arg := range args {
		numObj := arg.(*obj.NumberObject).Value
		switch numObj.(type) {
		case num.Complex:
			hasComplex = true
		case num.RNumber:
			hasRNumber = true
		default:
			return 3 // not a recognized number type
		}
	}

	if hasComplex && hasRNumber {
		return 2 // mixed
	} else if hasComplex {
		return 1 // all complex
	} else {
		return 0 // all RNumber
	}
}

func Eq(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 2 {
		return &Error{Message: "eq? requires exactly 2 arguments"}
	}

	// In our implementation, we'll use pointer equality for eq?
	// This is a simplified version - a full implementation would need to check
	// if the objects are the same in memory
	a, b := args[0], args[1]

	// Same object reference
	if a == b {
		return &obj.BooleanObject{Value: true}
	}

	// Different types are never eq?
	if a.Type() != b.Type() {
		return &obj.BooleanObject{Value: false}
	}

	// For certain types, we need to check if they represent the same value
	// This is a simplified implementation - in a full R4RS implementation,
	// eq? would be more restrictive than eqv?
	switch a.Type() {
	case obj.BOOLEAN_OBJ:
		boolA := a.(*obj.BooleanObject)
		boolB := b.(*obj.BooleanObject)
		return &obj.BooleanObject{Value: boolA.Value == boolB.Value}

	case obj.NULL_OBJ:
		// All null objects are eq
		return &obj.BooleanObject{Value: true}

	case obj.SYMBOL_OBJ:
		// Symbols with the same name are eq
		symA := a.(*obj.SymbolObject)
		symB := b.(*obj.SymbolObject)
		return &obj.BooleanObject{Value: symA.Value == symB.Value}

	default:
		// For other types, only the same object (pointer equality) counts as eq
		return &obj.BooleanObject{Value: false}
	}
}

func Eqv(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 2 {
		return &Error{Message: "eqv? requires exactly 2 arguments"}
	}

	a, b := args[0], args[1]

	// Same object reference
	if a == b {
		return &obj.BooleanObject{Value: true}
	}

	// Different types are never eqv?
	if a.Type() != b.Type() {
		return &obj.BooleanObject{Value: false}
	}

	// For certain types, we need to check if they represent the same value
	switch a.Type() {
	case obj.BOOLEAN_OBJ:
		boolA := a.(*obj.BooleanObject)
		boolB := b.(*obj.BooleanObject)
		return &obj.BooleanObject{Value: boolA.Value == boolB.Value}

	case obj.NUMBER_OBJ:
		// For numbers, check if they have the same value
		return equal(args, env)

	case obj.CHARACTER_OBJ:
		charA := a.(*obj.CharacterObject)
		charB := b.(*obj.CharacterObject)
		return &obj.BooleanObject{Value: charA.Value == charB.Value}

	case obj.STRING_OBJ:
		// For strings, check if they have the same value
		strA := a.(*obj.StringObject)
		strB := b.(*obj.StringObject)
		return &obj.BooleanObject{Value: strA.Value == strB.Value}

	case obj.SYMBOL_OBJ:
		// For symbols, check if they have the same name
		symA := a.(*obj.SymbolObject)
		symB := b.(*obj.SymbolObject)
		return &obj.BooleanObject{Value: symA.Value == symB.Value}

	case obj.NULL_OBJ:
		// All null objects are eqv
		return &obj.BooleanObject{Value: true}

	case obj.EMPTYLIST_OBJ:
		// All null objects are eqv
		return &obj.BooleanObject{Value: true}

	case obj.IDENTIFIER_OBJ:
		// Symbols with the same name are eqv
		idA := a.(*obj.IdentifierObject)
		idB := b.(*obj.IdentifierObject)
		return &obj.BooleanObject{Value: idA.Value == idB.Value}

	default:
		// For other types, only the same object (pointer equality) counts as eqv
		return &obj.BooleanObject{Value: false}
	}
}

func Equal(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 2 {
		return &Error{Message: "equal? requires exactly 2 arguments"}
	}

	return EqualRecursive(args[0], args[1])
}

func EqualRecursive(a, b obj.Object) obj.Object {
	// Same object reference
	if a == b {
		return &obj.BooleanObject{Value: true}
	}

	// Different types
	if a.Type() != b.Type() {
		return &obj.BooleanObject{Value: false}
	}

	// Handle different object types recursively
	switch a.Type() {
	case obj.BOOLEAN_OBJ:
		boolA := a.(*obj.BooleanObject)
		boolB := b.(*obj.BooleanObject)
		return &obj.BooleanObject{Value: boolA.Value == boolB.Value}

	case obj.NUMBER_OBJ:
		// Reuse the existing equal logic for numbers
		env := obj.NewEnvironment()
		return equal([]obj.Object{a, b}, env)

	case obj.CHARACTER_OBJ:
		charA := a.(*obj.CharacterObject)
		charB := b.(*obj.CharacterObject)
		return &obj.BooleanObject{Value: charA.Value == charB.Value}

	case obj.STRING_OBJ:
		strA := a.(*obj.StringObject)
		strB := b.(*obj.StringObject)
		return &obj.BooleanObject{Value: strA.Value == strB.Value}

	case obj.SYMBOL_OBJ:
		// For symbols, check if they have the same name
		symA := a.(*obj.SymbolObject)
		symB := b.(*obj.SymbolObject)
		return &obj.BooleanObject{Value: symA.Value == symB.Value}

	case obj.NULL_OBJ:
		// All null objects are equal
		return &obj.BooleanObject{Value: true}

	case obj.PAIR_OBJ:
		pairA := a.(*obj.PairObject)
		pairB := b.(*obj.PairObject)

		// Check for empty lists
		aIsNull := (pairA.Car == obj.EMPTY_LIST && pairA.Cdr == obj.EMPTY_LIST) ||
			(pairA.Car == nil && pairA.Cdr == nil)
		bIsNull := (pairB.Car == obj.EMPTY_LIST && pairB.Cdr == obj.EMPTY_LIST) ||
			(pairB.Car == nil && pairB.Cdr == nil)

		if aIsNull && bIsNull {
			return &obj.BooleanObject{Value: true}
		}

		if aIsNull || bIsNull {
			return &obj.BooleanObject{Value: false}
		}

		// Recursively compare car and cdr
		carEqual := EqualRecursive(
			convertToObjObject(pairA.Car),
			convertToObjObject(pairB.Car),
		)

		if !carEqual.(*obj.BooleanObject).Value {
			return carEqual
		}

		cdrEqual := EqualRecursive(
			convertToObjObject(pairA.Cdr),
			convertToObjObject(pairB.Cdr),
		)

		return cdrEqual

	default:
		// For other types, only the same object (pointer equality) counts as equal
		return &obj.BooleanObject{Value: false}
	}
}

func RegisterBuiltinProcedures(globalEnv *obj.Environment) {
	// Register arithmetic operations
	globalEnv.Set("+", &obj.Builtin{Fn: Add})
	globalEnv.Set("-", &obj.Builtin{Fn: Subtract})
	globalEnv.Set("*", &obj.Builtin{Fn: Multiply})
	globalEnv.Set("/", &obj.Builtin{Fn: Divide})

	// Register boolean operations
	globalEnv.Set("not", &obj.Builtin{Fn: Not})
	//globalEnv.Set("and", &obj.Builtin{Fn: And})
	//globalEnv.Set("or", &obj.Builtin{Fn: Or})

	// Register comparison operations
	globalEnv.Set("<", &obj.Builtin{Fn: lessThan})
	globalEnv.Set("<=", &obj.Builtin{Fn: lessThanOrEqual})
	globalEnv.Set("=", &obj.Builtin{Fn: equal})
	globalEnv.Set(">=", &obj.Builtin{Fn: greaterThanOrEqual})
	globalEnv.Set(">", &obj.Builtin{Fn: greaterThan})

	// Register equivalence predicates
	globalEnv.Set("eq?", &obj.Builtin{Fn: Eq})
	globalEnv.Set("eqv?", &obj.Builtin{Fn: Eqv})
	globalEnv.Set("equal?", &obj.Builtin{Fn: Equal})

	// Register additional numeric operations
	globalEnv.Set("exact?", &obj.Builtin{Fn: Exact})
	globalEnv.Set("inexact?", &obj.Builtin{Fn: Inexact})
	globalEnv.Set("odd?", &obj.Builtin{Fn: Odd})
	globalEnv.Set("even?", &obj.Builtin{Fn: Even})
	globalEnv.Set("max", &obj.Builtin{Fn: Max})
	globalEnv.Set("min", &obj.Builtin{Fn: Min})
	globalEnv.Set("abs", &obj.Builtin{Fn: Abs})
	globalEnv.Set("quotient", &obj.Builtin{Fn: Quotient})
	globalEnv.Set("remainder", &obj.Builtin{Fn: Remainder})
	globalEnv.Set("modulo", &obj.Builtin{Fn: Modulo})
	globalEnv.Set("gcd", &obj.Builtin{Fn: Gcd})
	globalEnv.Set("lcm", &obj.Builtin{Fn: Lcm})
	globalEnv.Set("floor", &obj.Builtin{Fn: FloorFunc})
	globalEnv.Set("ceiling", &obj.Builtin{Fn: CeilingFunc})
	globalEnv.Set("truncate", &obj.Builtin{Fn: TruncateFunc})
	globalEnv.Set("round", &obj.Builtin{Fn: RoundFunc})
	globalEnv.Set("rationalize", &obj.Builtin{Fn: Rationalize})
	globalEnv.Set("exp", &obj.Builtin{Fn: Exp})
	globalEnv.Set("log", &obj.Builtin{Fn: Log})
	globalEnv.Set("asin", &obj.Builtin{Fn: Asin})
	globalEnv.Set("acos", &obj.Builtin{Fn: Acos})
	globalEnv.Set("atan", &obj.Builtin{Fn: Atan})
	globalEnv.Set("sqrt", &obj.Builtin{Fn: Sqrt})
	globalEnv.Set("expt", &obj.Builtin{Fn: Expt})
	globalEnv.Set("make-rectangular", &obj.Builtin{Fn: MakeRectangular})
	globalEnv.Set("make-polar", &obj.Builtin{Fn: MakePolar})
	globalEnv.Set("real-part", &obj.Builtin{Fn: RealPart})
	globalEnv.Set("imag-part", &obj.Builtin{Fn: ImagPart})
	globalEnv.Set("magnitude", &obj.Builtin{Fn: Magnitude})
	globalEnv.Set("angle", &obj.Builtin{Fn: Angle})
	globalEnv.Set("exact->inexact", &obj.Builtin{Fn: ExactToInexact})
	globalEnv.Set("inexact->exact", &obj.Builtin{Fn: InexactToExact})
	globalEnv.Set("sin", &obj.Builtin{Fn: Sin})
	globalEnv.Set("cos", &obj.Builtin{Fn: Cos})
	globalEnv.Set("tan", &obj.Builtin{Fn: Tan})
	globalEnv.Set("number->string", &obj.Builtin{Fn: NumberToString})
	globalEnv.Set("string->number", &obj.Builtin{Fn: StringToNumber})
	globalEnv.Set("eq?", &obj.Builtin{Fn: Eq})
	globalEnv.Set("eqv?", &obj.Builtin{Fn: Eqv})
	globalEnv.Set("equal?", &obj.Builtin{Fn: Equal})

	// Register character operations
	globalEnv.Set("char=?", &obj.Builtin{Fn: CharEqual})
	globalEnv.Set("char<?", &obj.Builtin{Fn: CharLess})
	globalEnv.Set("char>?", &obj.Builtin{Fn: CharGreater})
	globalEnv.Set("char<=?", &obj.Builtin{Fn: CharLessEqual})
	globalEnv.Set("char>=?", &obj.Builtin{Fn: CharGreaterEqual})

	globalEnv.Set("char-ci=?", &obj.Builtin{Fn: CharCiEqual})
	globalEnv.Set("char-ci<?", &obj.Builtin{Fn: CharCiLess})
	globalEnv.Set("char-ci>?", &obj.Builtin{Fn: CharCiGreater})
	globalEnv.Set("char-ci<=?", &obj.Builtin{Fn: CharCiLessEqual})
	globalEnv.Set("char-ci>=?", &obj.Builtin{Fn: CharCiGreaterEqual})

	globalEnv.Set("char-alphabetic?", &obj.Builtin{Fn: CharAlphabetic})
	globalEnv.Set("char-numeric?", &obj.Builtin{Fn: CharNumeric})
	globalEnv.Set("char-whitespace?", &obj.Builtin{Fn: CharWhitespace})
	globalEnv.Set("char-upper-case?", &obj.Builtin{Fn: CharUpperCase})
	globalEnv.Set("char-lower-case?", &obj.Builtin{Fn: CharLowerCase})

	globalEnv.Set("char->integer", &obj.Builtin{Fn: CharToInteger})
	globalEnv.Set("integer->char", &obj.Builtin{Fn: IntegerToChar})

	globalEnv.Set("char-upcase", &obj.Builtin{Fn: CharUpcase})
	globalEnv.Set("char-downcase", &obj.Builtin{Fn: CharDowncase})

	// Register string operations
	globalEnv.Set("make-string", &obj.Builtin{Fn: MakeString})
	globalEnv.Set("string", &obj.Builtin{Fn: String})
	globalEnv.Set("string-length", &obj.Builtin{Fn: StringLength})
	globalEnv.Set("string-ref", &obj.Builtin{Fn: StringRef})
	globalEnv.Set("string-set!", &obj.Builtin{Fn: StringSet})
	globalEnv.Set("string=?", &obj.Builtin{Fn: StringEqual})
	globalEnv.Set("string-ci=?", &obj.Builtin{Fn: StringCiEqual})
	globalEnv.Set("string<?", &obj.Builtin{Fn: StringLess})
	globalEnv.Set("string>?", &obj.Builtin{Fn: StringGreater})
	globalEnv.Set("string<=?", &obj.Builtin{Fn: StringLessEqual})
	globalEnv.Set("string>=?", &obj.Builtin{Fn: StringGreaterEqual})
	globalEnv.Set("string-ci<?", &obj.Builtin{Fn: StringCiLess})
	globalEnv.Set("string-ci>?", &obj.Builtin{Fn: StringCiGreater})
	globalEnv.Set("string-ci<=?", &obj.Builtin{Fn: StringCiLessEqual})
	globalEnv.Set("string-ci>=?", &obj.Builtin{Fn: StringCiGreaterEqual})
	globalEnv.Set("substring", &obj.Builtin{Fn: Substring})
	globalEnv.Set("string-append", &obj.Builtin{Fn: StringAppend})
	globalEnv.Set("string->list", &obj.Builtin{Fn: StringToList})
	globalEnv.Set("list->string", &obj.Builtin{Fn: ListToString})
	globalEnv.Set("string-copy", &obj.Builtin{Fn: StringCopy})
	globalEnv.Set("string-fill!", &obj.Builtin{Fn: StringFill})

	// Register pair operations
	globalEnv.Set("cons", &obj.Builtin{Fn: Cons})
	globalEnv.Set("car", &obj.Builtin{Fn: Car})
	globalEnv.Set("cdr", &obj.Builtin{Fn: Cdr})
	globalEnv.Set("list", &obj.Builtin{Fn: List})
	globalEnv.Set("pair?", &obj.Builtin{Fn: IsPairProc})
	globalEnv.Set("set-car!", &obj.Builtin{Fn: SetCar})
	globalEnv.Set("set-cdr!", &obj.Builtin{Fn: SetCdr})

	// Register cXr functions (up to 4 levels deep)
	globalEnv.Set("caar", &obj.Builtin{Fn: Caar})
	globalEnv.Set("cadr", &obj.Builtin{Fn: Cadr})
	globalEnv.Set("cdar", &obj.Builtin{Fn: Cdar})
	globalEnv.Set("cddr", &obj.Builtin{Fn: Cddr})

	globalEnv.Set("caaar", &obj.Builtin{Fn: Caaar})
	globalEnv.Set("caadr", &obj.Builtin{Fn: Caadr})
	globalEnv.Set("cadar", &obj.Builtin{Fn: Cadar})
	globalEnv.Set("caddr", &obj.Builtin{Fn: Caddr})
	globalEnv.Set("cdaar", &obj.Builtin{Fn: Cdaar})
	globalEnv.Set("cdadr", &obj.Builtin{Fn: Cdadr})
	globalEnv.Set("cddar", &obj.Builtin{Fn: Cddar})
	globalEnv.Set("cdddr", &obj.Builtin{Fn: Cdddr})

	globalEnv.Set("caaaar", &obj.Builtin{Fn: Caaaar})
	globalEnv.Set("caaadr", &obj.Builtin{Fn: Caaadr})
	globalEnv.Set("caadar", &obj.Builtin{Fn: Caadar})
	globalEnv.Set("caaddr", &obj.Builtin{Fn: Caaddr})
	globalEnv.Set("cadaar", &obj.Builtin{Fn: Cadaar})
	globalEnv.Set("cadadr", &obj.Builtin{Fn: Cadadr})
	globalEnv.Set("caddar", &obj.Builtin{Fn: Caddar})
	globalEnv.Set("cadddr", &obj.Builtin{Fn: Cadddr})
	globalEnv.Set("cdaaar", &obj.Builtin{Fn: Cdaaar})
	globalEnv.Set("cdaadr", &obj.Builtin{Fn: Cdaadr})
	globalEnv.Set("cdadar", &obj.Builtin{Fn: Cdadar})
	globalEnv.Set("cdaddr", &obj.Builtin{Fn: Cdaddr})
	globalEnv.Set("cddaar", &obj.Builtin{Fn: Cddaar})
	globalEnv.Set("cddadr", &obj.Builtin{Fn: Cddadr})
	globalEnv.Set("cdddar", &obj.Builtin{Fn: Cdddar})
	globalEnv.Set("cddddr", &obj.Builtin{Fn: Cddddr})

	// Register list operations
	globalEnv.Set("null?", &obj.Builtin{Fn: NullP})
	globalEnv.Set("list?", &obj.Builtin{Fn: ListP})
	globalEnv.Set("length", &obj.Builtin{Fn: Length})
	globalEnv.Set("append", &obj.Builtin{Fn: Append})
	globalEnv.Set("reverse", &obj.Builtin{Fn: Reverse})
	globalEnv.Set("list-tail", &obj.Builtin{Fn: ListTail})
	globalEnv.Set("list-ref", &obj.Builtin{Fn: ListRef})
	globalEnv.Set("memq", &obj.Builtin{Fn: Memq})
	globalEnv.Set("memv", &obj.Builtin{Fn: Memv})
	globalEnv.Set("member", &obj.Builtin{Fn: Member})
	globalEnv.Set("assq", &obj.Builtin{Fn: Assq})
	globalEnv.Set("assv", &obj.Builtin{Fn: Assv})
	globalEnv.Set("assoc", &obj.Builtin{Fn: Assoc})

	// Register type predicate operations
	globalEnv.Set("boolean?", &obj.Builtin{Fn: IsBooleanProc})
	globalEnv.Set("symbol?", &obj.Builtin{Fn: IsSymbolProc})
	globalEnv.Set("number?", &obj.Builtin{Fn: IsNumberProc})
	globalEnv.Set("integer?", &obj.Builtin{Fn: IsIntegerProc})
	globalEnv.Set("rational?", &obj.Builtin{Fn: IsRationalProc})
	globalEnv.Set("real?", &obj.Builtin{Fn: IsRealProc})
	globalEnv.Set("complex?", &obj.Builtin{Fn: IsComplexProc})
	globalEnv.Set("char?", &obj.Builtin{Fn: IsCharProc})
	globalEnv.Set("string?", &obj.Builtin{Fn: IsStringProc})
	globalEnv.Set("procedure?", &obj.Builtin{Fn: IsProcedureProc})

	// Register symbol operations
	globalEnv.Set("symbol->string", &obj.Builtin{Fn: SymbolToString})
	globalEnv.Set("string->symbol", &obj.Builtin{Fn: StringToSymbol})

	// Register I/O procedures
	globalEnv.Set("call-with-input-file", &obj.Builtin{Fn: CallWithInputFile})
	globalEnv.Set("call-with-output-file", &obj.Builtin{Fn: CallWithOutputFile})
	globalEnv.Set("input-port?", &obj.Builtin{Fn: InputPortP})
	globalEnv.Set("output-port?", &obj.Builtin{Fn: OutputPortP})
	globalEnv.Set("current-input-port", &obj.Builtin{Fn: CurrentInputPort})
	globalEnv.Set("current-output-port", &obj.Builtin{Fn: CurrentOutputPort})
	globalEnv.Set("with-input-from-file", &obj.Builtin{Fn: WithInputFromFile})
	globalEnv.Set("with-output-to-file", &obj.Builtin{Fn: WithOutputToFile})
	globalEnv.Set("char-ready?", &obj.Builtin{Fn: CharReadyP})
	globalEnv.Set("open-input-file", &obj.Builtin{Fn: OpenInputFile})
	globalEnv.Set("open-output-file", &obj.Builtin{Fn: OpenOutputFile})
	globalEnv.Set("close-input-port", &obj.Builtin{Fn: CloseInputPort})
	globalEnv.Set("close-output-port", &obj.Builtin{Fn: CloseOutputPort})
	globalEnv.Set("read", &obj.Builtin{Fn: Read})
	globalEnv.Set("read-char", &obj.Builtin{Fn: ReadChar})
	globalEnv.Set("peek-char", &obj.Builtin{Fn: PeekChar})
	globalEnv.Set("eof-object?", &obj.Builtin{Fn: EofObject})
	globalEnv.Set("load", &obj.Builtin{Fn: Load})
	globalEnv.Set("transcript-on", &obj.Builtin{Fn: TranscriptOn})
	globalEnv.Set("transcript-off", &obj.Builtin{Fn: TranscriptOff})
	globalEnv.Set("write", &obj.Builtin{Fn: Write})
	globalEnv.Set("display", &obj.Builtin{Fn: Display})
	globalEnv.Set("newline", &obj.Builtin{Fn: Newline})
	globalEnv.Set("write-char", &obj.Builtin{Fn: WriteChar})

	// Register vector operations
	globalEnv.Set("vector?", &obj.Builtin{Fn: IsVectorProc})
	globalEnv.Set("make-vector", &obj.Builtin{Fn: MakeVector})
	globalEnv.Set("vector", &obj.Builtin{Fn: Vector})
	globalEnv.Set("vector-length", &obj.Builtin{Fn: VectorLength})
	globalEnv.Set("vector-ref", &obj.Builtin{Fn: VectorRef})
	globalEnv.Set("vector-set!", &obj.Builtin{Fn: VectorSet})
	globalEnv.Set("vector->list", &obj.Builtin{Fn: VectorToList})
	globalEnv.Set("list->vector", &obj.Builtin{Fn: ListToVector})
	globalEnv.Set("vector-fill!", &obj.Builtin{Fn: VectorFill})

	// Register apply, map, and for-each procedures
	globalEnv.Set("apply", &obj.Builtin{Fn: Apply})
	globalEnv.Set("map", &obj.Builtin{Fn: Map})
	globalEnv.Set("for-each", &obj.Builtin{Fn: ForEach})

	// Register call-with-current-continuation
	globalEnv.Set("call-with-current-continuation", &obj.Builtin{Fn: CallWithCurrentContinuation})
	globalEnv.Set("call/cc", &obj.Builtin{Fn: CallWithCurrentContinuation})

}

// CallWithCurrentContinuation implements the call-with-current-continuation procedure
// It captures the current continuation and passes it to the given procedure
func CallWithCurrentContinuation(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 1 {
		return &Error{Message: "call-with-current-continuation requires exactly 1 argument"}
	}

	// Create a continuation object that represents the current continuation
	// When invoked, it will abandon the current computation and return its argument
	continuation := &obj.ContinuationObject{
		Env: env.Copy(),
		ResumeFunc: func(values []obj.Object) (obj.Object, error) {
			// When called, signal that we should abandon the current computation
			if len(values) > 0 {
				return nil, &obj.ContinuationResultError{Result: values[0]}
			}
			return nil, &obj.ContinuationResultError{Result: obj.NULL_OBJECT}
		},
	}

	// Apply the procedure with the continuation as its argument
	result, err := applyObjectProcedure(args[0], []obj.Object{continuation}, env)
	if err != nil {
		// If it's a ContinuationResultError, return the result directly
		if contErr, ok := err.(*obj.ContinuationResultError); ok {
			return contErr.Result
		}
		return &Error{Message: err.Error()}
	}
	if result.Type() == obj.ERROR_OBJ {
		return &Error{Message: result.Inspect()}
	}
	return result
}
